package org.codeart.ioc.context;

import cn.hutool.core.util.StrUtil;
import org.codeart.ioc.annotation.Inject;
import org.codeart.ioc.config.BeanDefinition;
import org.codeart.ioc.exception.BeanException;
import org.codeart.ioc.reader.AnnotationDefinitionReader;
import org.codeart.ioc.support.BeanDefinitionRegistry;

import java.lang.reflect.Field;
import java.util.Map;

/**
 * 应用了注解的应用上下文
 */
public class AnnotatedApplicationContext extends AbstractApplicationContext {

    public AnnotatedApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        // 把单例对象集合注入进去
        this.beanDefinitionReader = new AnnotationDefinitionReader(this.singletonObjects);
        this.refresh();
    }

    @Override
    public Object getBean(String name) throws Exception {
        if (singletonObjects.containsKey(name)) {
            return singletonObjects.get(name);
        }
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);

        // 通过反射创建对象
        String className = beanDefinition.getClassName();
        Class<?> clazz = Class.forName(className);
        Object beanObj = clazz.newInstance();

        // 调用 set 方法注入 Bean
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(Inject.class)) {
                Inject annotation = field.getAnnotation(Inject.class);
                String beanName = annotation.value();
                if (StrUtil.isEmpty(beanName)) {
                    Class<?> aClass = field.getType();
                    Object bean = getBean(aClass);
                    field.set(beanObj, bean);
                } else {
                    Object bean = getBean(beanName);
                    field.set(beanObj, bean);
                }
            }
        }

        singletonObjects.put(name, beanObj);
        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
        Object bean = getBean(name);
        if (bean == null) {
            return null;
        }
        return clazz.cast(bean);
    }

    @Override
    public <T> T getBean(Class<?> requiredType) throws Exception {
        String ans = null;
        int count = 0;
        for (Map.Entry<String, Object> entry : this.singletonObjects.entrySet()) {
            Object obj = entry.getValue();
            Class<?> aClass = obj.getClass();
            if (aClass.getName().equals(requiredType.getName())) {
                ans = entry.getKey();
                count++;
            }
        }
        if (count > 1) {
            throw new BeanException(String.format("Expect 1 bean, but found more than 1: %s.\n", requiredType.getName()));
        }
        if (StrUtil.isNotEmpty(ans)) {
            return (T) getBean(ans);
        }

        BeanDefinitionRegistry registry = this.beanDefinitionReader.getRegistry();
        for (String beanDefinitionName : registry.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);
            if (beanDefinition.getClassName().equals(requiredType.getName())) {
                return (T) getBean(beanDefinitionName);
            }
        }
        throw new BeanException(String.format("Bean not found: %s.\n", requiredType.getName()));
    }
}
